package com.blogspot.javagamexyz.gamexyz.abilities;

import com.artemis.Entity;
import com.artemis.utils.Bag;
import com.artemis.utils.ImmutableBag;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.abilities.Action2.ActionProcessor;
import com.blogspot.javagamexyz.gamexyz.abilities.Action2.FieldCalculator;
import com.blogspot.javagamexyz.gamexyz.abilities.Action2.RangeCalculator;
import com.blogspot.javagamexyz.gamexyz.abilities.Action2.ScoreCalculator;
import com.blogspot.javagamexyz.gamexyz.components.Damage;
import com.blogspot.javagamexyz.gamexyz.components.Stats;
import com.blogspot.javagamexyz.gamexyz.custom.Globals;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.custom.Dice;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;
import com.blogspot.javagamexyz.gamexyz.utils.MyMath;
import com.blogspot.javagamexyz.gamexyz.systems.DiceRoll;

public class ActionFactory {

	//TODO ADD STUFF (and down the line can add flavor text to describe the trick based on surroundings {POCKET SAND!})
	//meleeAttack
	//rangedAttack (try to cover throwing here as well)
	//Trick (strength/agility/smarts opposed rolls)	
	//Called shot?
	

	//Melee ///////////////////////////////////////////////////////////////////////////////////////
	//TODO need to rework this since we can have a 'to-hit' stat and a 'damage' stat, although we might only need just one
	//basically, melee attacks will almost always be fighting to hit, and str to damage
	//ranged attacks will be shooting/casting/whatever to hit
	//so what we'll do is change statType to represent the 'to-hit- stat, and always just throw str dmg on melee attacks
	public static Action2 meleeAttack(String name, String statType, int mpCost, String tnType, Dice weapon, int successMod, int dmgMod){
		return new Action2(name, statType, mpCost, tnType, 1, 1, weapon, successMod, dmgMod,
		meleeAttackActionProcessor(),
		meleeAttackScoreCalculator(),
		defaultFieldCalculator(),
		defaultRangeCalculator());
	}
	
	
	public static ActionProcessor meleeAttackActionProcessor(){
		ActionProcessor actionProcessor = new ActionProcessor(){
			@Override
			public void process(Entity sourceE, Array<Entity> targets, Action2 action) {
				Stats source = sourceE.getComponent(Stats.class);
				source.magic -= action.mpCost;
				boolean hitOnce = false;			
				int tn, damage, hitRaise, statRoll;			
				for (Entity targetE : targets){ //this is for when there are multiple targets for an attack
					Stats target = targetE.getComponent(Stats.class);
					damage = 0;
					hitRaise = 0;
					statRoll = 0;					
					//Get the Target Number
					if (action.tnType.equals(Globals.DEFAULT)){
						tn = 4;
					}
					else{
						tn = target.getStatTotal(action.tnType);
					}					
					//Roll to determine hit
					System.out.println();
					System.out.println("Rolling to hit..."); //TODO Debug
					statRoll = source.getStatRoll(action.statType) + action.successMod;					
					System.out.println(source.name + "'s " + action.statType + " roll:	" + statRoll + "/" + source.getStatTotal(action.statType));//TODO Debug
					System.out.println(target.name + "'s " + action.tnType + " resist:	" + tn);//TODO Debug
					if (statRoll >= tn) { //HIT
						if (!hitOnce) {
							source.xp += 10;
							hitOnce = true;
						}
						//determine raise
						if (statRoll - tn >= 4){
							hitRaise = 1;
						}								
						//Roll damage: Strength + weapon + raise
						//NOTE don't roll wild on damage rolls (except with that one edge which we will implement eventually)		
						//TODO make sure you can use action.dice when it is null....
						System.out.println("Rolling damage...");
						damage = source.getStatRoll(Globals.STRENGTH) + DiceRoll.roll(action.dice) + (DiceRoll.roll(6) * hitRaise) + action.dmgMod;
						System.out.println(target.name + " is about to receive " + damage + " points of damage.");//TODO Debug
					}
					targetE.addComponent(new Damage(damage));
					targetE.changedInWorld();					
				}
			}
			
		};
		return actionProcessor;
	}
	//TODO maybe AI isn't choosing this because of a not so great score
	//TODO AI is only choosing it's class abilities, not innate abilities.
	public static ScoreCalculator meleeAttackScoreCalculator(){	
		ScoreCalculator scoreCalculator = new ScoreCalculator(){
			@Override
			public ImmutableBag<Float> calculateScore(Stats source, ImmutableBag<Stats> targets, Action2 action) {				
				// If we can't even cast it, then don't bother
				int MP = source.magic;
				if (action.mpCost > MP){ 					
					System.out.println("Not enough pp to use " + action.name);
					return null; 
				}
				
				Bag<Float> scoreBag = new Bag<Float>();
				
				// Get the cost to the source
				scoreBag.add(0.1f*(float)action.mpCost / (float)MP);
				
				System.out.println();
				System.out.println("Calculating " + action.name + " attack score for " + targets.size() + " targets..."); //TODO Debug
				
				// Get the scores for each target
				for (int i = 0; i < targets.size(); i++) {
					Stats target = targets.get(i);
					System.out.println("Target " + i + ":	" + target.name); //TODO Debug
					int HP = target.wounds + 1;
					if (target.shaken){
						HP++;
						System.out.println("Target is shaken"); //TODO Debug
					}
					
					//Damage potential
					int attackDmg = action.dice.getMaxRoll() + source.getStatRoll("strength") + action.dmgMod;
					int opponentToughness = target.getStatTotal(Globals.TOUGHNESS);
					int damage = (int)(MathUtils.random(0.8f,1.2f)*(attackDmg - opponentToughness));
					System.out.println("Damage potential: " + damage); //TODO Debug
					
					//Hit potential
					int skillLevel = source.getStatTotal(action.statType) + action.successMod;
					int opponentHit = target.getStatTotal(action.tnType);
					float chanceToHit = ((float)(skillLevel - opponentHit) / (float)skillLevel);
					System.out.println("Hit chance: " + chanceToHit); //TODO Debug
					scoreBag.add((float)(chanceToHit * damage * HP));
					System.out.println("Total Score: " + (chanceToHit * damage * HP)); //TODO Debug
					//basically: use your most damaging attack with the highest chance to hit against the most wounded/shaken target
				}
				
				return scoreBag;
			}
		};
		return scoreCalculator;
	}

		
	
	//Ranged ///////////////////////////////////////////////////////////////////////////////////////
	public static Action2 rangedAttack(String name, String statType, int mpCost, String tnType, Dice weapon, int successMod, int dmgMod){
		return new Action2(name, statType, mpCost, tnType, 1, 1, weapon, successMod, dmgMod,
		rangedAttackActionProcessor(),
		rangedAttackScoreCalculator(),
		defaultFieldCalculator(),
		defaultRangeCalculator());
	}
	
	public static ActionProcessor rangedAttackActionProcessor(){
		ActionProcessor actionProcessor = new ActionProcessor(){
			@Override
			public void process(Entity sourceE, Array<Entity> targets, Action2 action) {
				Stats source = sourceE.getComponent(Stats.class);
				source.magic -= action.mpCost;
				boolean hitOnce = false;			
				int tn, damage, hitRaise, statRoll;			
				for (Entity targetE : targets){ //this is for when there are multiple targets for an attack
					Stats target = targetE.getComponent(Stats.class);
					damage = 0;
					hitRaise = 0;
					statRoll = 0;					
					//Get the Target Number (shouldn't it always be 4? whatever)
					if (action.tnType.equals(Globals.DEFAULT)){
						tn = 4;
					}
					else{
						tn = target.getStatTotal(action.tnType);
					}					
					//Roll to determine hit
					//TODO COVER and RANGE
					statRoll = source.getStatRoll(action.statType) + action.successMod;
					if (statRoll >= tn) { //HIT
						if (!hitOnce) {
							source.xp += 10;
							hitOnce = true;
						}
						//determine raise
						if (statRoll - tn >= 4){
							hitRaise = 1;
						}								
						//Roll damage: weapon + raise
						//NOTE don't roll wild on damage rolls (except with that one edge which we will implement eventually)					
						//damage = target.applyDamage(DiceRoll.roll(action.dice) + (DiceRoll.roll(6) * hitRaise) + action.dmgMod);
						damage = DiceRoll.roll(action.dice) + (DiceRoll.roll(6) * hitRaise) + action.dmgMod;
					}
					targetE.addComponent(new Damage(damage));
					targetE.changedInWorld();					
				}
			}
			
		};
		return actionProcessor;
	}
	public static ScoreCalculator rangedAttackScoreCalculator(){	
		ScoreCalculator scoreCalculator = new ScoreCalculator(){
			@Override
			public ImmutableBag<Float> calculateScore(Stats source, ImmutableBag<Stats> targets, Action2 action) {				
				// If we can't even cast it, then don't bother
				int MP = source.magic;
				if (action.mpCost > MP) return null;
				
				Bag<Float> scoreBag = new Bag<Float>();
				
				// Get the cost to the source
				scoreBag.add(0.1f*(float)action.mpCost / (float)MP);
				
				// Get the scores for each target
				for (int i = 0; i < targets.size(); i++) {
					Stats target = targets.get(i);
					int HP = target.wounds + 1;
					if (target.shaken){
						HP++;
					}
					
					//Damage potential
					int attackDmg = action.dice.getMaxRoll() + action.dmgMod;
					int opponentToughness = target.getStatTotal("toughness");
					int damage = (int)(MathUtils.random(0.8f,1.2f)*(attackDmg - opponentToughness));
					
					//Hit potential 
					//TODO RANGE and COVER
					int skillLevel = source.getStatTotal(action.statType) + action.successMod;
					int opponentHit = target.getStatTotal(action.tnType);
					float chanceToHit = ((float)(skillLevel - opponentHit) / (float)skillLevel);
					scoreBag.add((float)(chanceToHit * damage * HP));
					//basically: use your most damaging attack with the highest chance to hit against the most wounded/shaken target
				}
				
				return scoreBag;
			}
		};
		return scoreCalculator;
	}	
		
	
	
	//default calculators////////////////////////////////////////////////////////////////////////////////////
	/*private static ScoreCalculator defaultScoreCalculator() {
		ScoreCalculator scoreCalculator = new ScoreCalculator(){
			@Override
			public ImmutableBag<Float> calculateScore(Stats source, ImmutableBag<Stats> targets, Action2 action) {				
				// If we can't even cast it, then don't bother
				int MP = source.magic;
				if (action.mpCost > MP) return null;
				
				Bag<Float> scoreBag = new Bag<Float>();
				
				// Get the cost to the source
				scoreBag.add(0.1f*(float)action.mpCost / (float)MP);
				
				// Get the scores for each target
				for (int i = 0; i < targets.size(); i++) {
					Stats target = targets.get(i);
					int HP = target.wounds + 1;
					if (target.shaken){
						HP++;
					}
					//Damage potential
					int attackDmg = action.dice.getMaxRoll() + action.dmgMod;
					int opponentToughness = target.getStatTotal("toughness");	//TODO we can have extra armor for certain types of attacks
					int damage = (int)(MathUtils.random(0.8f,1.2f)*(attackDmg - opponentToughness));
					
					//Hit potential
					int skillLevel = source.getStatTotal(action.statType) + action.successMod;
					int opponentHit = target.getStatTotal(action.tnType);
					float chanceToHit = ((float)(skillLevel - opponentHit) / (float)skillLevel);
					scoreBag.add((float)(chanceToHit * damage * HP));
				}
				
				return scoreBag;
			}
		};
		return scoreCalculator;
	}*/
	
	private static FieldCalculator defaultFieldCalculator(){
		FieldCalculator fieldCalculator = new FieldCalculator() {
			@Override
			public Array<Pair> getField(Pair target, Action2 action) {
				Array<Pair> field = MapTools.getNeighbors(target.x, target.y, action.field-1);
				field.add(target);
				return field;
			}			
		};
		return fieldCalculator;
	}
	
	private static RangeCalculator defaultRangeCalculator(){
			RangeCalculator rangeCalculator = new RangeCalculator() {
				@Override
				public Array<Pair> getRange(Pair source, Action2 action) {
					return MapTools.getNeighbors(source.x, source.y, action.range);
				}
			};
			return rangeCalculator;
		}
		
		
		
		
	//old shit//////////////////////////////////////////////////////////////////////////////////////////////
	public static Action2 physicalAttack(String name, int strength, int baseProbability, int range) {
		return new Action2(name,strength, 0, baseProbability, range, 1);
	}
	
	public static Action2 spinAttack(String name, int strength, int baseProbability) {
		Action2 action = new Action2(name, strength, 0, baseProbability, 0, -1);
		action.fieldCalculator = new FieldCalculator() {
			@Override
			public Array<Pair> getField(Pair target, Action2 action) {
				Array<Pair> field = MapTools.getNeighbors(target.x, target.y);
				return field;
			}
		};
		
		action.rangeCalculator = spellRangeCalculator();
		return action;
	}
	
	public static Action2 magicAttack(String name, int strength, int mpCost, int baseProbability, int range, int field) {
		return new Action2(name, strength, mpCost, baseProbability, range, field,
				magicAttackActionProcessor(),
				magicAttackScoreCalculator(),
				spellFieldCalculator(),
				spellRangeCalculator());
	}
	
	public static Action2 cure(String name, int strength, int mpCost, int range, int field) {
		return new Action2(name, strength, mpCost, 100, range, field,
				cureActionProcessor(),
				cureScoreCalculator(),
				spellFieldCalculator(),
				spellRangeCalculator());
	}
	
	private static ActionProcessor cureActionProcessor() {
		ActionProcessor ap = new ActionProcessor() {
			
			@Override
			public void process(Entity sourceE, Array<Entity> targets, Action2 action) {
				Stats source = sourceE.getComponent(Stats.class);
				source.magic -= action.mpCost;
				source.xp += 10;
				
				int cureAmt;
				for (Entity targetE : targets) {
					cureAmt = (int)(MathUtils.random(0.8f,1.2f)*(action.strength + source.getSmarts()));
					if (cureAmt < 1) cureAmt = 1;

					targetE.addComponent(new Damage(-1*cureAmt));
					targetE.changedInWorld();
				}
			}

		};
		return ap;
	}
	
	private static ScoreCalculator cureScoreCalculator() {
		ScoreCalculator sc = new ScoreCalculator() {
			
			@Override
			public ImmutableBag<Float> calculateScore(Stats source, ImmutableBag<Stats> targets, Action2 action) {
				if (action.mpCost > source.magic) return null;
				
				Bag<Float> scoreBag = new Bag<Float>();
				// Get cost for source
				scoreBag.add(0.1f*(float)action.mpCost / (float)source.magic);
				
				int cureAmt;
				// Get score for each target
				for (int i = 0; i < targets.size(); i++) {
					Stats target = targets.get(i);
					cureAmt = (int)(action.strength + source.getSmarts());
					//float curePercent = (float)MyMath.min(cureAmt, target.maxHealth - target.health) / (float)target.maxHealth;
					float curePercent = (float)MyMath.min(cureAmt, target.maxWounds - target.wounds) / (float)target.maxWounds;
					scoreBag.add(-1f*MathUtils.sinDeg(90*curePercent));
				}
				
				return scoreBag;
			}
		};
		return sc;
	}
	
	private static ActionProcessor magicAttackActionProcessor() {
		ActionProcessor ap = new ActionProcessor() {
			
			@Override
			public void process(Entity sourceE, Array<Entity> targets, Action2 action) {
				Stats source = sourceE.getComponent(Stats.class);
				source.magic -= action.mpCost;
				source.xp += 10;
				
				int dmgAmt;
				for (Entity targetE : targets) {
					Stats target = targetE.getComponent(Stats.class);
					//dmgAmt = (int)(MathUtils.random(0.8f,1.2f)*(action.strength + source.getStrength() - target.getToughness()));
					dmgAmt = 2;
					if (dmgAmt < 1) dmgAmt = 1;

					targetE.addComponent(new Damage(dmgAmt));
					targetE.changedInWorld();
				}
			}

		};
		return ap;
	}
	
	private static ScoreCalculator magicAttackScoreCalculator() {
		ScoreCalculator sc = new ScoreCalculator() {
			
			@Override
			public ImmutableBag<Float> calculateScore(Stats source, ImmutableBag<Stats> targets, Action2 action) {
				if (action.mpCost > source.magic) return null;
				
				Bag<Float> scoreBag = new Bag<Float>();
				// Get cost for source
				scoreBag.add(0.1f*(float)action.mpCost / (float)source.magic);
				
				// Get score for each target
				for (int i = 0; i < targets.size(); i++) {
					Stats target = targets.get(i);
					//int dmgAmt = (int)(action.strength + source.getStrength() - target.getToughness());
					int dmgAmt = 2;
					if (dmgAmt < 1) dmgAmt = 1;
					//float dmgPercent = (float)MyMath.min(dmgAmt, target.maxHealth - target.health) / (float)target.health; 
					float dmgPercent = (float)MyMath.min(dmgAmt, target.maxWounds - target.wounds) / (float)target.maxWounds;
					scoreBag.add(dmgPercent);
				}
				
				return scoreBag;
			}
		};
		return sc;
	}
	
	private static FieldCalculator spellFieldCalculator() {
		FieldCalculator fc = new FieldCalculator() {
			
			@Override
			public Array<Pair> getField(Pair target, Action2 action) {
				Array<Pair> field = MapTools.getNeighbors(target.x, target.y, action.field - 1);
				field.add(target);
				return field;
			}
		};
		return fc;
	}
	
	private static RangeCalculator spellRangeCalculator() {
		RangeCalculator rc = new RangeCalculator() {
			
			@Override
			public Array<Pair> getRange(Pair source, Action2 action) {
				Array<Pair> range = MapTools.getNeighbors(source.x, source.y, action.range);
				range.add(source);
				return range;
			}
		};
		return rc;
	}
	

}
